<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../static/js/vue.global.js "></script>
  <style>
    .box {
      position: relative;
    }
    .intro {
      margin: 10% auto 0 auto;
      text-align: center;
    }

    .card-wrap {
      position: relative;
      margin: 10% auto 0 auto;
    }

    .card-item {
      font-size: 28px;
      text-align: center;
      position: absolute;
      border-radius: 2px;
      box-sizing: border-box;
      background: #ddd;
      opacity: 1;
      cursor: pointer;
      transition: all 0.3s;
      box-shadow: 0px 3px 0 0 #fff, 0 8px 0 0 #ddd, 0 8px 0 2px #333, 0 0 0 2px #333;
    }

    .card-item:hover {
      transform: scale3d(1.1, 1.1, 1.1);
      z-index: 1;
    }

    .item-cover {
      pointer-events: none;
      box-shadow: 0px 3px 0 0 #999, 0 8px 0 0 #666, 0 8px 0 2px #000, 0 0 0 2px #000;
    }

    .item-cover:after {
      border-radius: 2px;
      content: "";
      position: absolute;
      width: 100%;
      height: 100%;
      left: 0;
      top: 0;
      background: #000;
      opacity: 0.55;
    }

    .card-tips {
      white-space: nowrap;
      position: absolute;
      left: 50%;
      top: 130%;
      transform: translate(-50%, 0);
      pointer-events: none;
    }

    .tools {
      position: absolute;
      top: 200%;
      width: 100%;
      left: 0;
      text-align: center;
    }
    .clear-item {
      pointer-events: none;
    }
  </style>
</head>
<body>
<div id="app">
  <div v-if="step === 0" class="intro">
    <div>
      横向卡片最大平铺排数
      <input
          v-model="option.x" min="2"
          max="10" type="range"
      > {{ option.x }}
    </div>
    <div>
      纵向卡片最大平铺排数
      <input
          v-model="option.y" min="2"
          max="10" type="range"
      > {{ option.y }}
    </div>
    <div>
      卡片最大堆叠层数
      <input
          v-model="option.z" min="2"
          max="10" type="range"
      > {{ option.z }}
    </div>
    <div>
      卡片密度
      <input
          v-model="option.cardRandom" min="0"
          max="1" step="0.1"
          type="range"
      >
      {{ option.cardRandom }}
    </div>
    <div>
      最大卡片种类
      <input
          v-model="option.maxCardType" min="3"
          max="14" step="1"
          type="range"
      >
      {{ option.maxCardType }}
    </div>
    <br>
    <button @click="startGame">开始游戏</button>
  </div>
  <div v-else-if="step === 2" class="intro">
    <h1>{{ result ? "You Win！🎉" : "You Lose!😢" }}</h1>
    <button @click="rePlay">再来一轮</button>
    <button @click="setGame">难度调节</button>
  </div>
  <div v-else class="box">
    <div class="card-wrap" :style="cardWrapStyle">
      <div
          v-for="item in cardItemList"
          :key="item.key"
          :class="{'item-cover': item.cover}"
          class="card-item"
          :style="item.style"
          @click="clickCard(item)"
      >
        {{ item.content }}
      </div>
      <div
          v-for="item in penddingList"
          :key="item.key"
          class="card-item"
          :style="item.style"
      >
        {{ item.content }}
      </div>
      <div
          v-for="item in clearList"
          :key="item.key"
          class="card-item clear-item"
          :style="item.style"
      >
        {{ item.content }}
      </div>
      <div
          v-for="item in saveList"
          :key="item.key"
          class="card-item"
          :style="item.style"
          @click="clickSaveCard(item)"
      >
        {{ item.content }}
      </div>
      <p class="card-tips">
        剩余空位:{{ 7 - penddingList.length }}/7；已消除:{{ clearList.length }}/{{
        cardItemList.length + penddingList.length + saveList.length + clearList.length
        }}
      </p>
    </div>
    <div class="tools">
      道具：
      <button :disabled="!tools.save" @click="saveCard">取出3个卡片</button>
      <button :disabled="!tools.rand" @click="randCard">随机</button>
      <button @click="rePlay">再来一轮</button>
    </div>
  </div>
</div>

<script>
  const { createApp } = Vue;

  class CardItem {
    static x = 20;
    static y = 21;
    static colorType = {
      1: {background: '#FFB7DD'},
      2: {background: '#FFCCCC'},
      3: {background: '#FFC8B4'},
      4: {background: '#FFDDAA'},
      5: {background: '#FFEE99'},
      6: {background: '#FFFFBB'},
      7: {background: '#EEFFBB'},
      8: {background: '#CCFF99'},
      9: {background: '#99FF99'},
      10: {background: '#BBFFEE'},
      11: {background: '#AAFFEE'},
      12: {background: '#99FFFF'},
      13: {background: '#CCEEFF'},
      14: {background: '#CCDDFF'}
    };
    static contentType = {
      1: '🥕',
      2: '✂️',
      3: '🥦',
      4: '🥛',
      5: '🌊',
      6: '🧤',
      7: '🧵',
      8: '🌱',
      9: '🔨',
      10: '🌽',
      11: '🌾',
      12: '🐑',
      13: '🪵',
      14: '🔥'
    };
    constructor({x, y, z, key}) {
      this.x = x;
      this.y = y;
      this.z = z;
      this.key = key;
      const offset = z * 10;
      this.val = key;
      this.style = {
        top: y * CardItem.y + offset + 'px',
        left: x * CardItem.x + offset + 'px',
        width: CardItem.x * 2 - 2 + 'px',
        height: CardItem.y * 2 - 8 + 'px'
      };
    }

    setValue(val) {
      this.val = val;
      this.content = CardItem.contentType[val];
      Object.assign(this.style, CardItem.colorType[val]);
    }
  }

  createApp({
    data() {
      return {
        option: {
          x: 6,
          y: 4,
          z: 8,
          cardRandom: 0.2,
          maxCardType: 11
        },
        step: 0,
        win: false,
        cardMap: [],
        cardItemList: [],
        penddingList: [],
        clearList: [],
        saveList: [],
        calcValueList: [],
        maxWidth: 0,
        maxHeight: 0,
        tools: {
          save: true,
          rand: true
        },
        timer: 0
      };
    },
    computed: {
      cardWrapStyle() {
        return {
          width: (this.maxWidth + 2) * CardItem.x + 'px',
          height: (this.maxHeight + 1) * CardItem.y + 'px'
        };
      },
      leftOffset() {
        const wrapWidth = (this.maxWidth + 2) * CardItem.x;
        return (wrapWidth - 7 * CardItem.x * 2) / 2;
      }
    },
    methods: {
      randCard() {
        if (!this.tools.rand) {
          return;
        }
        this.tools.rand = false;
        const length = this.cardItemList.length;
        this.cardItemList.forEach(item => {
          const randNum = Math.floor(length * Math.random());
          const newItem = this.cardItemList[randNum];
          let temp;
          temp = item.style.left;
          item.style.left = newItem.style.left;
          newItem.style.left = temp;
          temp = item.style.top;
          item.style.top = newItem.style.top;
          newItem.style.top = temp;
          temp = item.x;
          item.x = newItem.x;
          newItem.x = temp;
          temp = item.y;
          item.y = newItem.y;
          newItem.y = temp;
          temp = item.z;
          item.z = newItem.z;
          newItem.z = temp;
        });

        this.cardItemList.sort((a, b) => a.z - b.z);
        this.calcCover();
      },
      saveCard() {
        if (!this.tools.save) {
          return false;
        }
        this.tools.save = false;
        this.saveList = this.penddingList.slice(0, 3);
        setTimeout(() => {
          this.saveList.forEach((item, index) => {
            item.style.top = '110%';
            item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
            this.calcValueList[item.val]--;
          });
        }, 0);
        this.penddingList = this.penddingList.slice(3);
        this.penddingList.forEach((item, index) => {
          item.style.top = '160%';
          item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
        });
      },
      initGame() {
        this.step = 1;
        this.getMap(this.option);
        this.penddingList = [];
        this.clearList = [];
        this.saveList = [];
        this.tools.save = true;
        this.tools.rand = true;
        this.setCardValue({maxCardType: Number(this.option.maxCardType)});
        this.calcCover();
      },
      // 表示地图最大为 x * y 张牌，最多有 z 层
      getMap({x, y, z, cardRandom} = {}) {
        this.maxWidth = (x - 1) * 2;
        this.maxHeight = (y - 1) * 2 + 1;
        const cardMap = new Array(z);
        const cardItemList = [];
        let key = 0;
        // 地图初始化
        for (let k = 0; k < z; k++) {
          cardMap[k] = new Array(this.maxHeight);
          for (let i = 0; i <= this.maxHeight; i++) {
            cardMap[k][i] = new Array(this.maxWidth).fill(0);
          }
        }
        for (let k = 0; k < z; k++) {
          const shrink = Math.floor((z - k) / 3);
          // 行
          for (let i = shrink; i < this.maxHeight - shrink; i++) {
            // 列，对称设置
            const mid = Math.ceil((this.maxWidth - shrink) / 2);
            for (let j = shrink; j <= mid; j++) {
              let canSetCard = true;
              if (j > 0 && cardMap[k][i][j - 1]) {
                // 左边不能有牌
                canSetCard = false;
              }
              else if (i > 0 && cardMap[k][i - 1][j]) {
                // 上边不能有牌
                canSetCard = false;
              }
              else if (i > 0 && j > 0 && cardMap[k][i - 1][j - 1]) {
                // 左上不能有牌
                canSetCard = false;
              }
              else if (i > 0 && cardMap[k][i - 1][j + 1]) {
                // 右上不能有牌
                canSetCard = false;
              }
              else if (k > 0 && cardMap[k - 1][i][j]) {
                // 正底不能有牌
                canSetCard = false;
              }
              else if (Math.random() >= cardRandom) {
                canSetCard = false;
              }
              if (canSetCard) {
                key++;
                const cardItem = new CardItem({x: j, y: i, z: k, key});
                cardMap[k][i][j] = cardItem;
                cardItemList.push(cardItem);
                // 对称放置
                if (j < mid) {
                  key++;
                  const cardItem = new CardItem({
                    x: this.maxWidth - j,
                    y: i,
                    z: k,
                    key
                  });
                  cardMap[k][i][j] = cardItem;
                  cardItemList.push(cardItem);
                }
              }
            }
          }
        }
        cardItemList.reverse();
        for (let i = 1; i <= key % 3; i++) {
          const clearItem = cardItemList.pop();
          cardMap[clearItem.z][clearItem.y][clearItem.x] = 0;
        }
        cardItemList.reverse();
        this.cardMap = cardMap;
        this.cardItemList = cardItemList;
      },
      setCardValue({maxCardType} = {}) {
        // 卡片种类
        const valStack = new Array(maxCardType);
        this.calcValueList = new Array(maxCardType + 1).fill(0);
        // 给卡片设置值
        this.cardItemList.forEach(item => {
          const value = Math.ceil(Math.random() * maxCardType);
          if (valStack[value]) {
            valStack[value].push(item);
            if (valStack[value].length === 3) {
              valStack[value].forEach(item => {
                item.setValue(value);
              });
              valStack[value] = null;
            }
          }
          else {
            valStack[value] = [item];
          }
        });

        let count = 2;
        // console.log(valStack)
        valStack.forEach(list => {
          list
          && list.forEach(item => {
            count++;
            item.setValue(Math.floor(count / 3));
          });
        });
      },
      // 计算遮挡关系
      calcCover() {
        // 构建一个遮挡 map
        const coverMap = new Array(this.maxHeight);
        for (let i = 0; i <= this.maxHeight; i++) {
          coverMap[i] = new Array(this.maxWidth).fill(false);
        }

        // 从后往前，后面的层数高
        for (let i = this.cardItemList.length - 1; i >= 0; i--) {
          const item = this.cardItemList[i];
          const {x, y, z} = item;
          if (coverMap[y][x]) {
            item.cover = true;
          }
          else if (coverMap[y][x + 1]) {
            item.cover = true;
          }
          else if (coverMap[y + 1][x]) {
            item.cover = true;
          }
          else if (coverMap[y + 1][x + 1]) {
            item.cover = true;
          }
          else {
            item.cover = false;
          }
          coverMap[y][x] = true;
          coverMap[y + 1][x] = true;
          coverMap[y][x + 1] = true;
          coverMap[y + 1][x + 1] = true;
        }
      },
      clickSaveCard(item) {
        this.cardItemList.push(item);
        const index = this.saveList.indexOf(item);
        this.saveList = this.saveList
          .slice(0, index)
          .concat(this.saveList.slice(index + 1));
        this.clickCard(item);
      },
      removeThree() {
        this.penddingList.some(item => {
          if (this.calcValueList[item.val] === 3) {
            this.penddingList.forEach(newItem => {
              if (newItem.val === item.val) {
                this.clearList.push(newItem);
              }
            });
            setTimeout(() => {
              this.clearList.forEach((item, index) => {
                item.style.left = this.leftOffset - 60 + 'px';
              });
            }, 300);

            this.penddingList = this.penddingList.filter(newItem => {
              return newItem.val !== item.val;
            });
            this.penddingList.forEach((item, index) => {
              item.style.top = '160%';
              item.style.left = this.leftOffset + index * CardItem.x * 2 + 'px';
            });
            this.calcValueList[item.val] = 0;
            if (this.cardItemList.length === 0) {
              this.step = 2;
              this.result = true;
            }
          }
        });

        if (this.penddingList.length >= 7) {
          this.step = 2;
          this.result = false;
        }
      },
      // 点击卡片
      clickCard(item) {
        clearTimeout(this.timer);
        this.removeThree();
        this.penddingList.push(item);
        const index = this.cardItemList.indexOf(item);
        this.cardItemList = this.cardItemList
          .slice(0, index)
          .concat(this.cardItemList.slice(index + 1));
        this.calcCover();
        this.calcValueList[item.val]++;
        setTimeout(() => {
          item.style.top = '160%';
          item.style.left
            = this.leftOffset + (this.penddingList.length - 1) * CardItem.x * 2 + 'px';
        }, 0);

        this.timer = setTimeout(() => {
          this.removeThree();
        }, 500);
      },
      // 开始
      startGame() {
        this.initGame();
      },
      // 设置
      setGame() {
        this.step = 0;
      },
      // 重来
      rePlay() {
        this.initGame();
      }
    }
  }).mount('#app');
</script>



</body>
</html>
